use std::path::{Path, PathBuf};
use std::sync::atomic::{AtomicBool, Ordering};
use fuse3::raw::reply::ReplyStatFs;
use maglev::Maglev;
use nix::errno::Errno;
use nix::sys::statfs::{statfs, Statfs};

use tokio::{fs, fs::File, fs::OpenOptions};
use tracing::{debug, error, info, trace, warn};

use crate::error::FileSystemError;

#[derive(Debug, Default, Clone, Eq, PartialEq)]
pub struct StorageStat {
    pub blocks: u64,
    pub bfree: u64,
    pub bavail: u64,
    pub files: u64,
    pub ffree: u64,
    pub bsize: u32,
    pub namelen: u32,
    pub frsize: u32,
}

impl From<Statfs> for StorageStat {
    fn from(st: Statfs) -> Self {
        let mut stat = StorageStat {
            blocks: st.blocks(),
            bfree: st.blocks_free(),
            bavail: st.blocks_available(),
            files: st.files(),
            ffree: st.files_free(),
            bsize: 4096,
            namelen: 64,
            frsize: 4096,
        };
        let block_size = st.block_size() as u32;
        if stat.bsize != block_size {
            stat.bsize = block_size;
            stat.frsize = block_size;
        }
        stat
    }
}

pub struct Storage {
    path: PathBuf,
    stat: StorageStat,
    status: AtomicBool,
}

impl Storage {
    pub fn new(path: PathBuf) -> Self {
        let (stat, status) = match statfs(&path) {
            Ok(st) => {
                (StorageStat::from(st), true)
            },
            Err(e) => {
                error!("path stat error, path: {}, error: {}", path.display(), e);
                (StorageStat::default(), false)
            }
        };
        Storage {
            path,
            stat,
            status: AtomicBool::new(status),
        }
    }

    pub fn path(&self) -> PathBuf {
        self.path.clone()
    }

    pub async fn mkdir(&self, path: &Path) -> Result<PathBuf, FileSystemError> {
        let absolute = self.path.join(path.strip_prefix("/").unwrap());
        if absolute.exists() {
            return Ok(absolute)
        }
        if let Err(e) = fs::create_dir_all(&absolute).await {
            warn!("mkdir {} error: {}", absolute.display(), e);
            self.status.store(false, Ordering::SeqCst);
            Err(FileSystemError::LocalFilesystemError(e))
        } else {
            info!("mkdir success. path {}", absolute.display());
            Ok(absolute)
        }
    }

    pub async fn create(&self, path: &Path) -> Result<File, FileSystemError> {
        let absolute = self.path.join(path.strip_prefix("/").unwrap());
        if absolute.exists() {
            let r = match File::open(absolute).await {
                Ok(file) => Ok(file),
                Err(e) => Err(FileSystemError::LocalFilesystemError(e))
            };
            return r
        }
        info!("create node {}", absolute.display());
        if let Some(parent) = absolute.parent() {
            if !parent.exists() {
                if let Err(e) = fs::create_dir_all(&parent).await {
                    warn!("create parent {} error: {}", parent.display(), e);
                    self.status.store(false, Ordering::SeqCst);
                    return Err(FileSystemError::LocalFilesystemError(e))
                }
            }
        }
        let file = OpenOptions::new().read(true).write(true).create(true).open(absolute).await;
        match file {
            Ok(f) => Ok(f),
            Err(e) => {
                self.status.store(false, Ordering::SeqCst);
                Err(FileSystemError::LocalFilesystemError(e))
            }
        }
    }

    pub async fn open(&self, path: &Path) -> Result<File, FileSystemError> {
        let absolute = self.path.join(path.strip_prefix("/").unwrap());
        match File::open(absolute).await {
            Ok(file) => Ok(file),
            Err(e) => Err(FileSystemError::LocalFilesystemError(e))
        }
    }

    pub fn stat(&self) -> Result<Statfs, Errno> {
        match statfs(&self.path) {
            Ok(st) => {
                if !self.status.load(Ordering::SeqCst) {
                    self.status.store(true, Ordering::SeqCst);
                }
                Ok(st)
            },
            Err(e) => {
                self.status.store(false, Ordering::SeqCst);
                Err(e)
            }
        }
    }
}

pub struct LocalStorage {
    blocks: Vec<Storage>,
}

impl LocalStorage {
    pub fn new(root: Vec<PathBuf>) -> Self {
        if root.is_empty() {
            panic!("local storage must be set.")
        }
        let mut blocks = Vec::new();
        for p in root {
            let storage = Storage::new(p);
            blocks.push(storage);
        }
        LocalStorage {
            blocks
        }
    }

    pub async fn mkdir(&self, path: &Path) -> Result<(), FileSystemError> {
        let index = self.choose_block(path);
        let _p = self.blocks[index].mkdir(path).await?;
        Ok(())
    }

    pub async fn create(&self, path: &Path) -> Result<(usize, File), FileSystemError> {
        let index = self.choose_block(path);
        let file = self.blocks[index].create(path).await?;
        Ok((index, file))
    }

    pub async fn open(&self, root: usize, path: &Path) -> Result<File, FileSystemError> {
        let file = self.blocks[root].open(path).await?;
        Ok(file)
    }

    pub async fn rename(&self, index: usize, source: &Path, target: &Path) -> Result<(), FileSystemError> {
        let root = self.blocks[index].path();
        let source = root.join(source.strip_prefix("/").unwrap());
        let target = root.join(target.strip_prefix("/").unwrap());
        debug!("rename, root {}, source {}, target {}", index, source.display(), target.display());
        fs::rename(source, target).await.map_err(|e| {
            FileSystemError::LocalFilesystemError(e)
        })
    }

    pub async fn mvdir(&self, source: &Path, target: &Path) -> Result<(), FileSystemError> {
        for p in &self.blocks {
            let root = p.path();
            let source = root.join(source.strip_prefix("/").unwrap());
            let target = root.join(target.strip_prefix("/").unwrap());
            if source.exists() {
                debug!("mvdir source {}, target {}", source.display(), target.display());
                if let Err(e) = fs::rename(source, target).await {
                    return Err(FileSystemError::LocalFilesystemError(e))
                }
            }
        }
        Ok(())
    }

    pub async fn remove(&self, index: usize, path: &Path) -> Result<(), FileSystemError> {
        let root = self.blocks[index].path();
        let source = root.join(path.strip_prefix("/").unwrap());
        debug!("remove node {}, {}", index, source.display());
        if source.exists() {
            fs::remove_file(source).await.map_err(|e| {
                FileSystemError::LocalFilesystemError(e)
            })
        } else {
            warn!("file with path {} not exists.", source.display());
            Ok(())
        }
    }

    pub async fn rmdir(&self, path: &Path) -> Result<(), FileSystemError> {
        for p in &self.blocks {
            let source = p.path.join(path.strip_prefix("/").unwrap());
            if source.exists() {
                debug!("rmdir {}", source.display());
                if let Err(e) = fs::remove_dir(source).await {
                    return Err(FileSystemError::LocalFilesystemError(e))
                }
            }
        }
        Ok(())
    }

    pub fn storage_stat(&self) -> ReplyStatFs {
        let mut reply = ReplyStatFs {
            blocks: 0,
            bfree: 0,
            bavail: 0,
            files: 0,
            ffree: 0,
            bsize: 4096,
            namelen: 64,
            frsize: 4096,
        };
        for v in &self.blocks {
            match v.stat() {
                Err(e) => error!("file system stat error, path {}, error {}", v.path.display(), e),
                Ok(st) => {
                    reply.blocks = reply.blocks + st.blocks();
                    reply.bfree = reply.bfree + st.blocks_free();
                    reply.bavail = reply.bavail + st.blocks_available();
                    reply.files = reply.files + st.files();
                    reply.ffree = reply.ffree + st.files_free();
                    let block_size = st.block_size() as u32;
                    if reply.bsize != block_size {
                        reply.bsize = block_size;
                        reply.frsize = block_size;
                    }
                }
            }
        }

        reply
    }

    fn choose_block(&self, path: &Path) -> usize {
        let vec: Vec<usize> = self.blocks.iter()
            .enumerate()
            .filter(|(_index, storage)|storage.status.load(Ordering::SeqCst))
            .map(|(index, ..)|index)
            .collect();
        let lev = Maglev::new(vec);
        let file_name = path.file_name().unwrap().to_str().unwrap();
        lev[file_name]
    }
}